#include "compat_event_epoll.h"
#include "compat_event_posix.h"

#if (TARGET_OS == OS_POSIX)

/*
 * The first epoll version has been introduced in Linux 2.5.44.  The
 * interface was changed several times since then and the final version
 * of epoll_create(), epoll_ctl(), epoll_wait(), and EPOLLET mode has
 * been introduced in Linux 2.6.0 and is supported since glibc 2.3.2.
 *
 * EPOLLET mode did not work reliable in early implementaions and in
 * Linux 2.4 backport.
 *
 * EPOLLONESHOT             Linux 2.6.2,  glibc 2.3.
 * EPOLLRDHUP               Linux 2.6.17, glibc 2.8.
 * epoll_pwait()            Linux 2.6.19, glibc 2.6.
 * signalfd()               Linux 2.6.22, glibc 2.7.
 * eventfd()                Linux 2.6.22, glibc 2.7.
 * timerfd_create()         Linux 2.6.25, glibc 2.8.
 * epoll_create1()          Linux 2.6.27, glibc 2.9.
 * signalfd4()              Linux 2.6.27, glibc 2.9.
 * eventfd2()               Linux 2.6.27, glibc 2.9.
 * accept4()                Linux 2.6.28, glibc 2.10.
 * eventfd2(EFD_SEMAPHORE)  Linux 2.6.30, glibc 2.10.
 * EPOLLEXCLUSIVE           Linux 4.5, glibc 2.24.
 */

#include <sys/timerfd.h>

#ifndef __USE_GNU
#define __USE_GNU
#endif

#ifndef EPOLLRDHUP
#define EPOLLRDHUP 0x2000
#endif // !EPOLLRDHUP

#ifndef is_epoll_err
#define is_epoll_err(ev) ((ev & EPOLLERR) || (ev & EPOLLHUP) || (!(ev & EPOLLIN)))
#endif

// EPOLLRDHUP

#define epoll_use 1

#define ev_base_event (EPOLLET | EPOLLERR)
#ifdef EPOLLEXCLUSIVE
#define ev_poll_event (EPOLLIN /*| EPOLLEXCLUSIVE*/)
#else
#define ev_poll_event (EPOLLIN)
#endif
#define ev_conn_event          (EPOLLOUT)
#define ev_read_event          (EPOLLIN | EPOLLRDHUP)
#define ev_write_event         (EPOLLOUT)
#define ev_clear_event         (0)
#define ev_oneshot_write_event (EPOLLOUT | EPOLLONESHOT)
#define ev_oneshot_read_event  (EPOLLIN | EPOLLONESHOT)

static int is_use_epoll_rdhup(epoll_op_t* base, int ep)
{
    int use_epoll_rdhup;

    int s[2], events;
    struct epoll_event ee;

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == -1) {
        return S_ERROR;
    }

    ee.events = EPOLLET | EPOLLIN | EPOLLRDHUP;

    if (base->sys_epoll_ctl(ep, EPOLL_CTL_ADD, s[0], &ee) == -1) {
        goto failed;
    }

    if (close(s[1]) == -1) {
        s[1] = -1;
        goto failed;
    }

    s[1] = -1;

    events = base->sys_epoll_wait(ep, &ee, 1, 5000);

    if (events == -1) {
        goto failed;
    }

    if (events) {
        use_epoll_rdhup = ee.events & EPOLLRDHUP;
    }

    return use_epoll_rdhup ? S_SUCCESS : S_ERROR;

failed:

    if (s[1] != -1 && close(s[1]) == -1) {
        return S_ERROR;
    }

    if (close(s[0]) == -1) {
        return S_ERROR;
    }

    return S_ERROR;
}

static int epoll_notify_handler(epoll_op_t* base)
{
    return S_SUCCESS;
}

static int sys_hook_api(epoll_op_t* base)
{
    base->sys_epoll_create = (epoll_create_fn)dlsym(RTLD_NEXT, "epoll_create");
    rc_error(base->sys_epoll_create != NULL, S_ERROR);

    base->sys_epoll_wait = (epoll_wait_fn)dlsym(RTLD_NEXT, "epoll_wait");
    rc_error(base->sys_epoll_wait != NULL, S_ERROR);

    base->sys_epoll_ctl = (epoll_ctl_fn)dlsym(RTLD_NEXT, "epoll_ctl");
    rc_error(base->sys_epoll_ctl != NULL, S_ERROR);

    base->sys_eventfd = (sys_eventfd_fn)dlsym(RTLD_NEXT, "eventfd");
    rc_error(base->sys_eventfd != NULL, S_ERROR);

    return S_SUCCESS;
}

static uint32_t epoll_event_add(eevent_t* ptr, uint32_t op)
{
    uint32_t event = 0;
    event = ptr->op;
    event |= op;
    return event;
}
static uint32_t epoll_event_del(eevent_t* ptr, uint32_t op)
{
    uint32_t event = 0;
    event = ptr->op;
    event &= ~op;
    return event;
}
static uint32_t get_epoll_op(eevent_t* ptr)
{
    rc_error(ptr != NULL, S_ERROR);
    return ptr->op;
}
static int set_epoll_op(eevent_t* ptr, uint32_t op)
{
    rc_error(ptr != NULL, S_ERROR);
    ptr->op = op;
    return S_SUCCESS;
}
static int epoll_op(epoll_op_t* base, evoper_t* oper, eevent_t* ptr, int op)
{
    struct epoll_event ee;
    ee.events = get_epoll_op(ptr);
    ee.data.u32 = 0;
    ee.data.u64 = 0;
    ee.data.ptr = oper;

    // ctl_mod --> EPOLLEXCLUSIVE --> 22 sys_epoll_ctl return EINVAL
    if (base->sys_epoll_ctl(base->epfd, op, oper->fd, &ee) == 0) {
        return S_SUCCESS;
    }

    if (errno == ENOENT) {
        loge("epoll_ctl error %d, epfd=%d, fd=%d\n", errno, base->epfd, oper->fd);
        goto end;
    } else if (errno != EPERM) {
        loge("epoll_ctl error %d, epfd=%d, fd=%d\n", errno, base->epfd, oper->fd);
    }

    return S_ERROR;
end:
    return S_NOFOUND;
}
int epoll_alloc(evloop_t* loop, int size)
{
    int rc = S_ERROR;

    epoll_op_t* base = NULL;

    base = (epoll_op_t*)loop->mm_malloc(loop, 1, sizeof(epoll_op_t));
    rc_error(base != NULL, S_ERROR);

    base->size = size;
    base->notify_fd = INVALID_SOCKET;
    base->epfd = INVALID_SOCKET;
    base->sys_epoll_create = NULL;
    base->sys_epoll_wait = NULL;
    base->sys_epoll_ctl = NULL;
    base->sys_eventfd = NULL;
    base->mode = 0;

    rc = sys_hook_api(base);
    if (rc != S_SUCCESS)
        goto err;

    base->epfd = base->sys_epoll_create(base->size);
    if (base->epfd == INVALID_SOCKET)
        goto err;

    base->notify_fd = base->sys_eventfd(0, 0);
    if (base->notify_fd != INVALID_SOCKET) {
        base->notify_handler = epoll_notify_handler;
        base->size += 1;
    }

    base->mode = ev_base_event;

    rc = is_use_epoll_rdhup(base, base->epfd);
    if (rc != S_SUCCESS)
        goto err;

    rc = socket_cloexec(base->epfd);
    if (rc != S_SUCCESS)
        goto err;

    base->poll = NULL;
    base->poll = loop->mm_malloc(loop, base->size, sizeof(struct epoll_event));
    if (base->poll == NULL)
        goto err;
	
	evloop_queue_init(&base->queue);

    return evloop_bind_evbase(loop, base);
err:
    loop->mm_free(loop, base);
    return S_ERROR;
}
int epoll_dealloc(evloop_t* loop)
{
    rc_error(loop != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evloop_evbase(loop);
    loop->mm_free(loop, base->poll);
    return S_SUCCESS;
}
int epoll_handler(evloop_t* loop, ev_op ev)
{
    int rc = S_ERROR;
    rc_error(loop != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evloop_evbase(loop);
    rc_error(base != NULL, S_ERROR);
    base->ev = ev;
    return rc;
}
int epoll_alloc_evoper(evloop_t* loop, evoper_t* oper)
{
    rc_error(loop != NULL, S_ERROR);
    rc_error(oper != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evloop_evbase(loop);
    rc_error(base != NULL, S_ERROR);

    eevent_t* ptr = (eevent_t*)heap_calloc(1, sizeof(eevent_t));
    rc_error(ptr != NULL, S_ERROR);

    evoper_bind_private(oper, ptr);

    uint32_t op = ev_base_event;
    set_epoll_op(ptr, op);

    evoper_op_init(oper);
    // https://elixir.bootlin.com/linux/v4.19.316/source/fs/eventpoll.c#L2047
    return epoll_op(base, oper, ptr, EPOLL_CTL_ADD);
}
int epoll_dealloc_evoper(evloop_t* loop, evoper_t* oper)
{
    int rc = S_ERROR;
    rc_error(oper != NULL, S_ERROR);
    rc_error(loop != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evloop_evbase(loop);
    rc_error(base != NULL, S_ERROR);
    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

    set_epoll_op(ptr, ev_clear_event);
    rc = epoll_op(base, oper, ptr, EPOLL_CTL_DEL);

    heap_free(ptr);

    return rc;
}
int epoll_enable_read(evloop_t* loop, evoper_t* oper, short event, ev_time_t tv)
{
    int rc = S_SUCCESS;
    rc_error(oper != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);
    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

    // add epoll
    uint32_t op = epoll_event_add(ptr, ev_read_event);
    set_epoll_op(ptr, op);
    epoll_op(base, oper, ptr, EPOLL_CTL_MOD);
 
    return rc;
}
int epoll_enable_write(evloop_t* loop, evoper_t* oper, short event, ev_time_t tv)
{
    int rc = S_SUCCESS;
    rc_error(oper != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);
    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

 
    // send
    rc = evoper_tcpsock_send(oper, S_SUCCESS, 0, base->ev);
    if (rc == S_PENDING) {
        // add epoll
        uint32_t op = epoll_event_add(ptr, ev_write_event);
        set_epoll_op(ptr, op);
        rc = epoll_op(base, oper, ptr, EPOLL_CTL_MOD);
    }

    return rc;
}
int epoll_disable_read(evloop_t* loop, evoper_t* oper, short event)
{
    rc_error(oper != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);

    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

    uint32_t op = epoll_event_del(ptr, ev_read_event);
    set_epoll_op(ptr, op);

    return epoll_op(base, oper, ptr, EPOLL_CTL_MOD);
}
int epoll_disable_write(evloop_t* loop, evoper_t* oper, short event)
{
    rc_error(oper != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);

    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

    evoper_write_op(oper, op_ev_write);

    uint32_t op = epoll_event_del(ptr, ev_read_event);
    set_epoll_op(ptr, op);

    return epoll_op(base, oper, ptr, EPOLL_CTL_MOD);
}
int epoll_cancel(evloop_t* loop, evoper_t* oper)
{
    epoll_op_t* base = (epoll_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);

    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

    set_epoll_op(ptr, ev_clear_event);

    return epoll_op(base, oper, ptr, EPOLL_CTL_MOD);
}
int epoll_open(evloop_t* loop, evoper_t* oper, const evaddr_t* addr)
{
    return S_ERROR;
}
int epoll_poll(evloop_t* loop, evoper_t* oper)
{
    rc_error(oper != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);

    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

    set_epoll_op(ptr, ev_poll_event | ev_base_event);

    return epoll_op(base, oper, ptr, EPOLL_CTL_MOD);
}
int epoll_add_connect(evloop_t* loop, evoper_t* oper, const evaddr_t* addr, ev_time_t tv)
{
    int rc = S_ERROR;
    rc_error(oper != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);

    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

    rc = evoper_sock_connect(oper, addr);

    if (rc == S_PENDING) {
        uint32_t op = ev_conn_event | ev_base_event;
        set_epoll_op(ptr, op);
        rc = epoll_op(base, oper, ptr, EPOLL_CTL_MOD);
    }

    return rc;
}
int epoll_del_connect(evloop_t* loop, evoper_t* oper)
{
    rc_error(oper != NULL, S_ERROR);
    epoll_op_t* base = (epoll_op_t*)evoper_evbase(oper);
    rc_error(base != NULL, S_ERROR);

    eevent_t* ptr = (eevent_t*)evoper_private(oper);
    rc_error(ptr != NULL, S_ERROR);

    uint32_t op = epoll_event_del(ptr, ev_conn_event);
    set_epoll_op(ptr, op);
    epoll_op(base, oper, ptr, EPOLL_CTL_MOD);

    return S_SUCCESS;
}
// https://elixir.bootlin.com/linux/v2.6.26/source/fs/eventpoll.c#L99
#define MAX_TIMEOUT_MSEC (35 * 60 * 1000)

static int epoll_cmd_poll(epoll_op_t* base, evloop_t* loop, crt_msec msec)
{
    crt_msec timeout = 0;

    timeout = (msec == 0 || timeout > MAX_TIMEOUT_MSEC) ? MAX_TIMEOUT_MSEC : msec;

    int fds = 0;
    fds = base->sys_epoll_wait(base->epfd, base->poll, base->size, timeout);

    crterr err = crt_geterror();

    int n = 0;

    for (n = 0; n < fds; n++) {
        evoper_t* oper = NULL;
        uint32_t events = 0;
        rc_error_break(base->ev != NULL);

        oper = base->poll[n].data.ptr;
        rc_error_continue(oper != NULL);
        events = base->poll[n].events;
        rc_error_continue(oper->fd != INVALID_SOCKET);

		if (base->poll[n].data.fd == base->notify_fd) {
            if (base->notify_handler(base) == S_EXIT) {
                break;
            }
        }

        // error
        if (events & EPOLLERR) {
            evoper_err_op(oper, ev_on);
            complete_sock_close(loop, S_SUCCESS, err, oper, base->ev);
            continue;
        }

        // shutdown
        if (events & EPOLLHUP) {
            evoper_close_op(oper, ev_on);
            complete_sock_close(loop, S_SUCCESS, err, oper, base->ev);
            continue;
        }

        if (events & EPOLLIN) {
            if (events & EPOLLRDHUP) {
                evoper_close_op(oper, ev_on);
                complete_sock_close(loop, S_SUCCESS, err, oper, base->ev);
                continue;
            }

            if (oper->accept == op_ev_accepting) {
                complete_sock_accept(loop, S_SUCCESS, err, oper, NULL, base->ev);
                continue;
            }

            complete_sock_read(loop, S_SUCCESS, err, oper, base->ev);

        }
        if (events & EPOLLOUT) {
            if (oper->conn == op_ev_connecting) {
                complete_sock_connect(loop, S_SUCCESS, err, oper, base->ev);
                continue;
            }

            complete_sock_write(loop, S_SUCCESS, err, oper, base->ev);

            if (evoper_writeing(oper) == S_SUCCESS)
				evoper_tcpsock_send(oper, S_SUCCESS, err, base->ev);
        }
    }

    return S_SUCCESS;
}
static int epoll_cmd_exit(const epoll_op_t* base, evloop_t* loop)
{
    return S_SUCCESS;
}
static int epoll_dispatch(evloop_t* loop, short event, crt_msec time)
{
    int rc = S_SUCCESS;

    epoll_op_t* base = (epoll_op_t*)evloop_evbase(loop);

    switch (event) {
    case op_cmd_poll:
        rc = epoll_cmd_poll(base, loop, time);
        break;
    case op_cmd_exit:
        rc = epoll_cmd_exit(base, loop);
        break;
    default:
        break;
    }

    return rc;
}

int epoll_evop_t(evsysop_t* op)
{
    op->op_alloc = epoll_alloc;
    op->op_dealloc = epoll_dealloc;

    op->op_handler = epoll_handler;

    op->op_alloc_evoper = epoll_alloc_evoper;
    op->op_dealloc_evoper = epoll_dealloc_evoper;

    op->op_enable_read = epoll_enable_read;
    op->op_enable_write = epoll_enable_write;
    op->op_disable_read = epoll_disable_read;
    op->op_disable_write = epoll_disable_write;

    op->op_cancel = epoll_cancel;
    op->op_open = epoll_open;
    op->op_poll = epoll_poll;
    op->op_add_connect = epoll_add_connect;
    op->op_del_connect = epoll_del_connect;

    op->op_dispatch = epoll_dispatch;

    return S_SUCCESS;
}
int epollevop_set_oper(eevent_t* ptr, evoper_t* oper)
{
	ptr->oper = oper;
	return S_SUCCESS;
}
#endif

